import { createHash } from 'crypto';
import { resolve, posix, parse } from 'path';
import { readFileSync, writeFileSync } from 'fs';

import FX from 'fs-extra';
import Moment from 'moment';
import GZIP from 'node-gzip';

import { C, dirCacheAudio, dirCacheExtract } from '../../../lib/global.js';
import Biffer from './lib/Biffer.js';
import Wog from './lib/Wog.js';
import { unZstd } from './lib/Tool.js';
import Manifest from './Manifest/index.js';

import makeFilesHash from './makefilesHash.js';
import { fetchEntry, fetchManifest } from './fetch.js';


const regexSuffix = /\.(zh_cn|en_us)\.wad(\.client)?$/i;
const fileMetas = resolve(C.path.vowad, '@meta.json');

const extractBNK = function(file) {
	const filesWEM = [];

	try {
		const wpkBiffuer = new Biffer(resolve(dirCacheExtract, file));

		FX.emptyDirSync(dirCacheAudio);

		// eslint-disable-next-line no-unused-vars
		const [magic, version, count] = wpkBiffuer.unpack('4sLL');

		const headerOffsets = wpkBiffuer.unpack(`${count}L`);

		for(const headerOffset of headerOffsets) {
			wpkBiffuer.seek(headerOffset);

			const [offset, size, nameLength] = wpkBiffuer.unpack('LLL');
			const name = Buffer.from([...wpkBiffuer.raw(nameLength * 2)].filter(byte => byte)).toString('utf8');
			const fileWEM = resolve(dirCacheAudio, name);

			const bufferWEM = wpkBiffuer.buffer.slice(offset, offset + size);

			writeFileSync(fileWEM, bufferWEM);

			filesWEM.push([
				createHash('md5').update(bufferWEM).digest('hex'),
				fileWEM
			]);
		}

	} catch(error) { void 0; }

	return filesWEM;
};


const updateBNK = function(fileBNK, metaWAD, wog) {
	const nameBNK = parse(fileBNK).base;
	const hashBNK = createHash('md5').update(readFileSync(fileBNK)).digest('hex');

	const metaBNK = metaWAD.bnk[nameBNK] ?? (metaWAD.bnk[nameBNK] = { unFinish: true, hash: null, time: null, wem: {}, });


	if(metaBNK.hash != hashBNK || metaBNK.unFinish) {
		wog.addSub(hashBNK, wog.warn, nameBNK, `哈希不一致`);

		metaBNK.wem = {};
		metaBNK.hash = hashBNK;
		metaBNK.time = Moment().format('YYYY-MM-DD HH:mm:ss');

		const filesWEM = extractBNK(fileBNK, wog);

		for(const [hashWEM, fileWEM] of filesWEM) {
			const nameWEM = parse(fileWEM).base;

			if(metaBNK.wem[nameWEM] != hashWEM) {
				metaBNK.wem[nameWEM] = hashWEM;

				// wog.addSub(hashWEM, wog.warn, nameWEM, `哈希不一致`);
			}
		}

		delete metaBNK.unFinish;
	}
};

const extractWAD = async function(pathWAD, takeMap) {
	const bifferWAD = new Biffer(pathWAD);

	// eslint-disable-next-line no-unused-vars
	const [magic, versionMajor, versionMinor] = bifferWAD.unpack('2sBB');

	if(versionMajor == 1) {
		bifferWAD.seek(8);
	}
	else if(versionMajor == 2) {
		bifferWAD.seek(100);
	}
	else if(versionMajor == 3) {
		bifferWAD.seek(268);
	}

	const [sizeEntry] = bifferWAD.unpack('I');
	const filesExtract = [];

	for(let i = 0; i < sizeEntry; i++) {
		// eslint-disable-next-line no-unused-vars
		let hash, offset, size, type, compressedSize, duplicate, sha256;

		if(versionMajor == 1) {
			[hash, offset, compressedSize, size, type] = bifferWAD.unpack('QIIII');
		}
		else {
			// eslint-disable-next-line no-unused-vars
			[hash, offset, compressedSize, size, type, duplicate, , , sha256] = bifferWAD.unpack('QIIIBBBBQ');
		}

		const saveName = takeMap[hash];
		if(saveName) {
			const fileBuffer = bifferWAD.buffer.slice(offset, offset + compressedSize);

			const pathSave = resolve(dirCacheExtract, saveName);

			filesExtract.push(pathSave);

			if(type == 0) {
				writeFileSync(pathSave, fileBuffer);
			}
			else if(type == 1) {
				writeFileSync(pathSave, await GZIP.ungzip(fileBuffer));
			}
			else if(type == 2) {
				throw 'unused extract';
				// const [n] = new Biffer(fileBuffer).unpack('L');
				// target = data[4: 4 + n].rstrip(b'\0').decode('utf-8')
			}
			else if(type == 3) {
				await unZstd(pathSave, fileBuffer);
			}
		}
	}

	return filesExtract.sort();
};

export const updateWAD = async function(file, metaWAD = { unFinish: true, hash: null, time: null, bnk: {}, }, newWog) {
	const base = posix.parse(file.name).base;

	const wogExtract = newWog();
	wogExtract.add(wogExtract.ready, `检查${base}`, '', {
		1: { stat: wogExtract.ready, title: '下载子包', text: '' },
		2: { stat: wogExtract.ready, title: '解压文件', text: '' },
		3: { stat: wogExtract.ready, title: '下载WAD', text: '' },
	});

	wogExtract.set(3, wogExtract.wait, '等待解压...');
	const [hashWAD, pathWAD] = await file.extract(C.gameServer.cdn, resolve(C.path.vowad, base), wogExtract);
	wogExtract.set(3, wogExtract.done, '已解压');

	if(metaWAD.hash != hashWAD || metaWAD.unFinish) {
		wogExtract.set(0, wogExtract.warn, '哈希不一致');
		metaWAD.hash = hashWAD;
		metaWAD.time = Moment().format('YYYY-MM-DD HH:mm:ss');

		const slot = base.replace(regexSuffix, '');
		const lang = base.match(regexSuffix)[1];

		const filesHash = makeFilesHash(slot, lang);

		if(!filesHash.length) {
			const filesBNK = await extractWAD(pathWAD, filesHash);

			for(const file of filesBNK) {
				updateBNK(file, metaWAD, wogExtract);
			}
		}

		delete metaWAD.unFinish;
	}
	else if(metaWAD.hash == hashWAD) {
		wogExtract.set(0, wogExtract.done, '哈希一致');
	}

	return metaWAD;
};


const updateMeta = async W => {
	let meta;
	try {
		meta = FX.readJSONSync(fileMetas);
	}
	catch(error) {
		meta = { unFinish: true, version: 0, time: 0 };
	}


	const newWog = () => new Wog(W);
	newWog.W = W;


	const wogFetchEntry = newWog();
	const [urlManifest, versionLatest] = await fetchEntry(wogFetchEntry);
	// if(meta.version >= versionLatest && !meta.unFinish) {
	// 	wogFetchEntry.set(0, Wog.warn, `完成。当前版本(${meta.version}) >= 最新版本(${versionLatest})`);

	// 	return;
	// }
	meta.version = versionLatest;


	const manifest = new Manifest(urlManifest, versionLatest, await fetchManifest(urlManifest, versionLatest, newWog()));
	await manifest.parseRman(newWog());
	await manifest.parseBody(newWog());


	const filesUpdate = Object.values(manifest.files).filter(file =>
		file.name.startsWith('DATA/FINAL/Champions/') &&
		regexSuffix.test(file.name)
	);

	for(const file of filesUpdate) {
		const base = parse(file.name).base;
		const fileMeta = resolve(C.path.vowad, `${base}.meta.json`);

		let metaWADRaw;
		try { metaWADRaw = FX.readJSONSync(fileMeta); }
		catch(error) { void 0; }
		let metaWAD = await updateWAD(file, metaWADRaw, newWog);

		writeFileSync(fileMeta, JSON.stringify(metaWAD, null, '\t'));
		meta.time = Moment().format('YYYY-MM-DD HH:mm:ss');
		writeFileSync(fileMetas, JSON.stringify(meta, null, '\t'));
	}

	delete meta.unFinish;

	writeFileSync(fileMetas, JSON.stringify(meta, null, '\t'));

	W.cast('updateFinish');
};


export default updateMeta;
